/*
 * Copyright (C) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the 'License');
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an 'AS IS' BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "lottie_xcomponent_node.h"
#include "common/number_constant.h"
#include "node/lottie_manager.h"
#include "node/lottie_manager_map.h"
#include "task_pool/task_pool.h"
#include <arkui/native_interface.h>
#include <cstdint>
namespace LottieTurbo {

static std::map<std::string, OH_NativeXComponent_Callback *> &GetCallback()
{
    static std::map<std::string, OH_NativeXComponent_Callback *> callback;
    return callback;
}

static bool PushCallback(std::string id, OH_NativeXComponent_Callback *callback)
{
    if (callback == nullptr) {
        return false;
    }
    auto &map = GetCallback();
    auto it = map.find(id);
    if (it != map.end()) {
        map[id] = callback;
        return true;
    }
    return false;
}

static bool PopCallback(std::string id)
{
    auto &map = GetCallback();
    auto it = map.find(id);
    if (it != map.end()) {
        delete map[id];
        map[id] = nullptr;
        map.erase(id);
    }
    return true;
}

LottieXcomponentNode::LottieXcomponentNode(std::string xcomponentId)
{
    this->CreateXcomponentNode(xcomponentId);
}

LottieXcomponentNode::~LottieXcomponentNode()
{
    status_ = 1;
    arkUINativeNodeApi_->disposeNode(handle_);
}

ArkUI_NodeHandle LottieXcomponentNode::GetHandle() const
{
    return handle_;
}

void LottieXcomponentNode::CreateXcomponentNode(std::string xcomponentId)
{
    OH_ArkUI_GetModuleInterface(ARKUI_NATIVE_NODE, ArkUI_NativeNodeAPI_1, arkUINativeNodeApi_);
    if (!arkUINativeNodeApi_) {
        DRAWING_LOGE("LottieXcomponentNode::CreateXcomponentNode lottieId(%{public}s): Error arkUINativeNodeApi_ NULL",
                     !lottieHandler_ ? "" : lottieHandler_->GetLottieId().c_str());
        return;
    }
    handle_ = arkUINativeNodeApi_->createNode(ARKUI_NODE_XCOMPONENT);
    if (!handle_) {
        DRAWING_LOGE("LottieXcomponentNode::CreateXcomponentNode lottieId(%{public}s): Error handle_ NULL",
                     !lottieHandler_ ? "" : lottieHandler_->GetLottieId().c_str());
        return;
    }
    if (xcomponentId.length() <= 0) {
        DRAWING_LOGE("LottieXcomponentNode::CreateXcomponentNode lottieId(%{public}s): Error xcomponentId NULL",
                     !lottieHandler_ ? "" : lottieHandler_->GetLottieId().c_str());
        return;
    }
    ArkUI_AttributeItem item = {nullptr, NUM_1, xcomponentId.c_str()};
    arkUINativeNodeApi_->setAttribute(handle_, NODE_XCOMPONENT_ID, &item);
    ArkUI_NumberValue backgroundColorValue[] = {{.u32 = 0x00000000}};
    ArkUI_AttributeItem backgroundColorItem = {backgroundColorValue,
                                               sizeof(backgroundColorValue) / sizeof(ArkUI_NumberValue)};
    arkUINativeNodeApi_->setAttribute(handle_, NODE_BACKGROUND_COLOR, &backgroundColorItem);
    OH_NativeXComponent_Callback *callback = new OH_NativeXComponent_Callback;
    callback->OnSurfaceCreated = OnSurfaceCreated;
    callback->OnSurfaceChanged = OnSurfaceChanged;
    callback->DispatchTouchEvent = OnDispatchTouchEvent;
    callback->OnSurfaceDestroyed = OnSurfaceDestroyed;
    PushCallback(xcomponentId, callback);
    nativeXComponent_ = OH_NativeXComponent_GetNativeXComponent(handle_);
    if (!nativeXComponent_) {
        DRAWING_LOGE("LottieXcomponentNode::CreateXcomponentNode lottieId(%{public}s): Error GetNativeXComponent "
                     "nativeXComponent_ NULL",
                     !lottieHandler_ ? "" : lottieHandler_->GetLottieId().c_str());
        return;
    }
    OH_NativeXComponent_RegisterCallback(nativeXComponent_, callback);
    LottieManagerMap::getInstance().Add(xcomponentId);
    lottieRender_ = std::make_shared<LottieRender>();
}

void LottieXcomponentNode::SetAnimator()
{
    if (!handle_ || !lottieHandler_) {
        DRAWING_LOGE("LottieXcomponentNode::SetAnimator lottieId(%{public}s): Error Init params NULL",
                     !lottieHandler_ ? "" : lottieHandler_->GetLottieId().c_str());
        return;
    }
    ArkUI_ContextHandle contextHandle = OH_ArkUI_GetContextByNode(handle_);
    if (!contextHandle) {
        DRAWING_LOGE("LottieXcomponentNode::SetAnimator lottieId(%{public}s): Error contextHandle NULL",
                     lottieHandler_->GetLottieId().c_str());
        return;
    }
    lottieHandler_->SetAnimator(contextHandle);
}

void LottieXcomponentNode::SetHandler(std::shared_ptr<LottieHandler> lottieHandler)
{
    if (!handle_ || !lottieHandler) {
        DRAWING_LOGE("LottieXcomponentNode::SetHandle lottieId(%{public}s): Error Init params NULL",
                     !lottieHandler ? "" : lottieHandler->GetLottieId().c_str());
        return;
    }
    lottieHandler_ = lottieHandler;
}

void LottieXcomponentNode::Init()
{
    status_ = 0;
    int32_t width = 0;
    int32_t height = 0;
    if (!handle_ || !lottieHandler_) {
        DRAWING_LOGE("LottieXcomponentNode::Init lottieId(%{public}s): Error Init params NULL",
                     !lottieHandler_ ? "" : lottieHandler_->GetLottieId().c_str());
        return;
    }
    lottieHandler_->SetRenderCallBack([this]() { this->RenderCurrentFrame(); });
    lottieHandler_->SetAnimatorCallBack([this]() { this->RenderAndAdvanceFrame(); });
    lottieHandler_->SetContentModeUpdateCallback([this]() { this->OnUpdateContentMode(); });
    LottieManagerMap::getInstance().SetHandleById(lottieHandler_->GetLottieId(), lottieHandler_);
    LottieManagerMap::getInstance().GetRectById(lottieHandler_->GetLottieId(), width, height);
    if (width != 0 && height != 0) {
        lottieHandler_->SetLottieSize(width, height);
        if (lottieHandler_->updateContentMode) {
            lottieHandler_->updateContentMode();
        }
        if (lottieHandler_->GetLottieConfig()->GetAutoplay()) {
            lottieHandler_->Play();
        } else if (lottieHandler_->renderAndAdvanceFrame) {
            lottieHandler_->renderAndAdvanceFrame();
        }
    }
}

void LottieXcomponentNode::RenderCurrentFrame()
{
    if (!lottieHandler_ || !lottieHandler_->dataManager_ || !lottieHandler_->lottieListener_) {
        DRAWING_LOGE("LottieXcomponentNode::RenderCurrentFrame lottieId(%{public}s): Error OnDraw params NULL",
                     !lottieHandler_ ? "" : lottieHandler_->GetLottieId().c_str());
        return;
    }
    float absoluteFrameToPlay = static_cast<float>(lottieHandler_->GetAbsoluteFrameToPlay());
    if (!lottieHandler_->GetIsSubframeEnabled()) {
        absoluteFrameToPlay = std::round(absoluteFrameToPlay);
    }
    uint32_t lottieFileTotalFrame = static_cast<uint32_t>(lottieHandler_->GetLottieFileTotalFrames());
    DRAWING_LOGD("OnDraw, absoluteFrameToPlay: %{public}f, lottieFileTotalFrame: %{public}d", absoluteFrameToPlay,
                 lottieFileTotalFrame);
    int64_t current = lottieHandler_->currentIndex_++;
    // 绝对帧大于动画文件总帧数时不绘制
    if (absoluteFrameToPlay < lottieFileTotalFrame) {
        ScheduleRenderTask(absoluteFrameToPlay, current);
    }
}

void LottieXcomponentNode::ScheduleRenderTask(float absoluteFrameToPlay, int64_t current)
{
    auto func = [](std::shared_ptr<LottieHandler> handler, CalculatePosition position,
                   std::shared_ptr<LottieRender> render, float ab, int64_t current) {
        /*
         * 这里添加丢帧逻辑，规则如下
         * 1. 当前需要渲染的帧和最新的一帧相差超过10帧，则开启丢帧模式
         * 2. 为了保证动画的连续性，丢帧时保证连续丢帧不超过N帧
         * 3. 通过总帧数 / 20 计算出来, N最大为3帧
         */
        auto loss = handler->GetTotalFrames() / handler->skipFrameBase;
        if (loss > handler->skipFrameLimits) {
            loss = handler->skipFrameLimits;
        }
        if (abs(current - handler->currentIndex_) >= handler->lossFrameLimits && handler->skip_ < loss) {
            handler->skip_++;
            return;
        }
        handler->skip_ = 0;

        if (handler->isDestroy_) {
            return;
        }

        if (handler->GetPlayState() == LottieHandler::PlayState::STOP ||
            handler->GetPlayState() == LottieHandler::PlayState::PAUSE) {
            handler->SetPlayState(LottieHandler::PlayState::END);
        }
        HITRACE(
            "Lottie::Render", auto lotLayerNode = handler->dataManager_->player->renderTree(
                                ab, position.width, position.height, position.keepAspectRatio);
            render->RenderTree(handler->GetLottieId(), lotLayerNode, (LottieTurbo::CalculatePosition *)&position);
        );

        auto func = [](LottieListener *listener, LottieEventType type) {
            if (listener) {
                listener->TriggerEvent(type);
            }
        };
        auto trigger = new LottieHandler::TriggerData;
        trigger->func = std::bind(func, handler->lottieListener_.get(), LottieEventType::ON_DRAW_FRAME);
        napi_call_threadsafe_function(handler->safeFunc_, trigger, napi_tsfn_blocking);
    };
    TaskPoolLocal::GetInstance().PushTask(func, lottieHandler_, position_, lottieRender_, absoluteFrameToPlay, current);
}

void LottieXcomponentNode::RenderAndAdvanceFrame()
{
    if (firstFrame_) {
        firstFrame_ = false;
    } else if (lottieHandler_ && lottieHandler_->dataManager_) {
        if (lottieHandler_->dataManager_->GetVisible() == false) {
            lottieHandler_->ResetLastTimeStamp();
            return;
        }
    }
    if (status_) {
        return;
    }
    if (lottieHandler_->lottieListener_) {
        auto func = [](LottieListener *listener, LottieEventType type) {
            if (listener) {
                listener->TriggerEvent(type);
            }
        };
        auto trigger = new LottieHandler::TriggerData;
        trigger->func = std::bind(func, lottieHandler_->lottieListener_.get(), LottieEventType::ON_ENTER_FRAME);
        napi_call_threadsafe_function(lottieHandler_->safeFunc_, trigger, napi_tsfn_blocking);
    }
    if (lottieHandler_ && lottieHandler_->animator_ &&
        (lottieHandler_->animator_->GetStatus() == Animator::AnimatorStatus::PLAY ||
         lottieHandler_->animator_->GetStatus() == Animator::AnimatorStatus::INIT)) {
        this->RenderCurrentFrame();
        lottieHandler_->AdvanceFrame();
    }
}

void LottieXcomponentNode::OnUpdateContentMode()
{
    size_t animWidth;
    size_t animHeight;
    std::string contentMode = lottieHandler_->GetLottieConfig()->GetContentMode();
    lottieHandler_->dataManager_->GetSize(animWidth, animHeight);
    int componentWidth = lottieHandler_->GetLottieSize().width;
    int componentHeight = lottieHandler_->GetLottieSize().height;
    float animAspectRatio = static_cast<float>(animWidth) / animHeight;
    float componentAspectRatio = static_cast<float>(componentWidth) / componentHeight;

    // 初始化位置
    position_.x = 0;
    position_.y = 0;
    position_.width = componentWidth;
    position_.height = componentHeight;

    // 根据contentMode设置缩放和对齐方式
    if (contentMode == "Contain") {
        position_.keepAspectRatio = true;
    } else if (contentMode == "Fill") {
        position_.keepAspectRatio = false;
    } else if (contentMode == "Cover") {
        bool useWidthScale = (animAspectRatio <= componentAspectRatio);
        float scale = useWidthScale ?
            static_cast<float>(componentWidth) / animWidth :
            static_cast<float>(componentHeight) / animHeight;

        if (useWidthScale) {
            position_.height = static_cast<int>(animHeight * scale);
            position_.y = -(position_.height - componentHeight + PIXEL_OFFSET) / CENTER_DIVISOR;
        } else {
            position_.width = static_cast<int>(animWidth * scale);
            position_.x = -(position_.width - componentWidth + PIXEL_OFFSET) / CENTER_DIVISOR;
        }
    } else if (contentMode == "Top" || contentMode == "Bottom") {
        bool useWidthScale = (animAspectRatio >= componentAspectRatio);
        float scale = useWidthScale ?
            static_cast<float>(componentWidth) / animWidth :
            static_cast<float>(componentHeight) / animHeight;

        if (useWidthScale) {
            position_.height = static_cast<int>(animHeight * scale);
            if (contentMode == "Bottom") {
                position_.y = componentHeight - position_.height;
            }
        } else {
            position_.width = static_cast<int>(animWidth * scale);
            if (contentMode == "Bottom") {
                position_.x = componentWidth - position_.width;
            }
        }
    } else {
        position_.keepAspectRatio = true;
    }
    lottieHandler_->dataManager_->InitRect(position_.width, position_.height);
}

void LottieXcomponentNode::RenderOnSurfaceChanged(OH_NativeXComponent *component, void *window, bool isOnSurfaceCreated)
{
    uint64_t width;
    uint64_t height;
    char idStr[OH_XCOMPONENT_ID_LEN_MAX + NUM_1] = {};
    uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + NUM_1;
    int32_t ret = OH_NativeXComponent_GetXComponentId(component, idStr, &idSize);
    if (ret != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) {
        DRAWING_LOGE("LottieXcomponentNode::RenderOnSurfaceChanged: Error GetXComponentId Failed");
        return;
    }
    ret = OH_NativeXComponent_GetXComponentSize(component, window, &width, &height);
    if (ret != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) {
        DRAWING_LOGE("LottieXcomponentNode::RenderOnSurfaceChanged: Error GetXComponentSize Failed");
        return;
    }
    DRAWING_LOGD("RenderOnSurfaceChanged ret=%{public}d width=%{public}lu,"
                 "height=%{public}lu id=%{public}s",
                 ret, width, height, idStr);
    if (isOnSurfaceCreated) {
        LottieManagerMap::getInstance().SetWindowById(idStr, window);
    }
    LottieManagerMap::getInstance().SetRectById(idStr, width, height);
    auto lottieHandler = LottieManagerMap::getInstance().GetHandleById(idStr);
    if (lottieHandler != nullptr) {
        lottieHandler->SetLottieSize(width, height);
        if (lottieHandler->updateContentMode) {
            lottieHandler->updateContentMode();
        }
        if (isOnSurfaceCreated && lottieHandler->GetLottieConfig()->GetAutoplay()) {
            lottieHandler->Play();
        } else if (lottieHandler->renderAndAdvanceFrame) {
            lottieHandler->renderAndAdvanceFrame();
        }
    } else {
        DRAWING_LOGW("LottieXcomponentNode::RenderOnSurfaceChanged: Warning LottieHandler not find");
    }
}

void LottieXcomponentNode::OnSurfaceCreated(OH_NativeXComponent *component, void *window)
{
    DRAWING_LOGD("OnSurfaceCreated");
    RenderOnSurfaceChanged(component, window, true);
}

void LottieXcomponentNode::OnSurfaceChanged(OH_NativeXComponent *component, void *window)
{
    DRAWING_LOGD("OnSurfaceChanged");
    RenderOnSurfaceChanged(component, window, false);
}

void LottieXcomponentNode::OnDispatchTouchEvent(OH_NativeXComponent *component, void *window)
{
    DRAWING_LOGD("OnDispatchTouchEvent");
}

void LottieXcomponentNode::OnSurfaceDestroyed(OH_NativeXComponent *component, void *window)
{
    char idStr[OH_XCOMPONENT_ID_LEN_MAX + NUM_1] = {};
    uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + NUM_1;
    int32_t ret = OH_NativeXComponent_GetXComponentId(component, idStr, &idSize);
    if (ret != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) {
        DRAWING_LOGE("LottieXcomponentNode::RenderOnSurfaceChanged: Error GetXComponentId Failed");
        return;
    }
    PopCallback(idStr);
    DRAWING_LOGD("OnSurfaceDestroyed lottieId(%{public}s)", idStr);
}

}